home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / teutil.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-09-13  |  31.4 KB  |  1,200 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     teutil.c
  4.  
  5.     This reusable module contains miscellaneous TextEdit utility routines.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <GestaltEqu.h>
  12. #include <Script.h>
  13. #include <ctype.h>
  14. #include <Drag.h>
  15.  
  16. #include "def.h"
  17. #include "teutil.h"
  18. #include "memutil.h"
  19. #include "drawutil.h"
  20. #include "tescroll.h"
  21.  
  22.  
  23.  
  24. /*----------------------------------------------------------------------------
  25.     HaveTEOutlineHiliteFeature 
  26.     
  27.     Determine whether or not this system supports the outline highlighting
  28.     feature.
  29.             
  30.     Exit:    function result = true if outline highlighting supported.
  31. ----------------------------------------------------------------------------*/
  32.  
  33. Boolean HaveTEOutlineHiliteFeature (void)
  34. {
  35.     OSErr err = noErr;
  36.     long result;
  37.  
  38.     err = Gestalt(gestaltTextEditVersion, &result);
  39.     return err == noErr && result >= gestaltTE4;
  40. }
  41.  
  42.  
  43.  
  44. /*----------------------------------------------------------------------------
  45.     HaveTEGetHiliteRgn 
  46.     
  47.     Determine whether or not this system supports the GetTEHiliteRgn
  48.     function.
  49.             
  50.     Exit:    function result = true if GetTEHiliteRgn function available.
  51. ----------------------------------------------------------------------------*/
  52.  
  53. Boolean HaveTEGetHiliteRgn (void)
  54. {
  55.     OSErr err = noErr;
  56.     long result;
  57.  
  58.     err = Gestalt(gestaltTEAttr, &result);
  59.     return err == noErr && (result >> gestaltTEHasGetHiliteRgn) & 1 != 0;
  60. }
  61.  
  62.  
  63.  
  64. /*----------------------------------------------------------------------------
  65.     MyTESetText 
  66.     
  67.     Set the text in a TextEdit record, with memory preflighting.
  68.             
  69.     Entry:    textPtr = pointer to text.
  70.             length = length of text.
  71.             theTE = handle to TextEdit record.
  72.     
  73.     Exit:    function result = error code.
  74. ----------------------------------------------------------------------------*/
  75.  
  76. OSErr MyTESetText (Ptr textPtr, long length, TEHandle theTE)
  77. {
  78.     long extraMem;
  79.  
  80.     extraMem = length - (**theTE).teLength;
  81.     if (extraMem > 0 && !MemoryAvailable(extraMem + 2000)) return memFullErr;
  82.     TESetText(textPtr, length, theTE);
  83.     return noErr;
  84. }
  85.  
  86.  
  87.  
  88. /*----------------------------------------------------------------------------
  89.     MyTEInsert 
  90.     
  91.     Insert text in a TextEdit record, with memory preflighting.
  92.             
  93.     Entry:    textPtr = pointer to text.
  94.             length = length of text.
  95.             theTE = handle to TextEdit record.
  96.     
  97.     Exit:    function result = error code.
  98. ----------------------------------------------------------------------------*/
  99.  
  100. OSErr MyTEInsert (Ptr textPtr, long length, TEHandle theTE)
  101. {
  102.     long extraMem;
  103.     
  104.     extraMem = (**theTE).selEnd - (**theTE).selStart + length;
  105.     if (extraMem > 0 && !MemoryAvailable(extraMem + 2000)) return memFullErr;
  106.     TEInsert(textPtr, length, theTE);
  107.     return noErr;
  108. }
  109.  
  110.  
  111.  
  112. /*----------------------------------------------------------------------------
  113.     MyTEGetScrapLen 
  114.     
  115.     Get the text scrap length.
  116.             
  117.     Exit:    function result = scrap length.
  118.                 = 0 if no text in scrap.
  119.                 = 0x8000 if text scrap too big.
  120. ----------------------------------------------------------------------------*/
  121.  
  122. long MyTEGetScrapLen (void)
  123. {
  124.     long len, offset;
  125.     OSErr err = noErr;
  126.     
  127.     len = GetScrap(nil, 'TEXT', &offset);
  128.     if (len < 0) return 0;
  129.     if (len > 0x7fff) return 0x8000;
  130.     err = TEFromScrap();
  131.     if (err == teScrapSizeErr) return 0x8000;
  132.     if (err != noErr) return 0;
  133.     return len;
  134. }
  135.  
  136.  
  137.  
  138. /*----------------------------------------------------------------------------
  139.     CharIsAWord 
  140.     
  141.     Determine whether or not a single character is a "word" (a letter or
  142.     digit).
  143.     
  144.     Entry:    str = pointer to string.
  145.             offset = offset in string of character.
  146.     
  147.     Exit:    function result = true if character is a "word".
  148. ----------------------------------------------------------------------------*/
  149.  
  150. static Boolean CharIsAWord (Ptr str, short offset)
  151. {
  152.     short x, charType, charClass;
  153.  
  154.     x = CharType(str, offset);
  155.     charType = x & smcTypeMask;
  156.     charClass = x & smcClassMask;
  157.     if (charType == smCharAscii || charType == smCharExtAscii) return true;
  158.     if (charType == smCharPunct && charClass == smPunctNumber) return true;
  159.     return false;
  160. }
  161.  
  162.  
  163.  
  164. /*----------------------------------------------------------------------------
  165.     IsWordStart 
  166.     
  167.     Determine whether or not a given offset in a string of text is the 
  168.     beginning of a word.
  169.     
  170.     Entry:    str = pointer to string.
  171.             len = length of string.
  172.             offset = offset in string.
  173.     
  174.     Exit:    function result = true if offset is at beginning of a word.
  175. ----------------------------------------------------------------------------*/
  176.  
  177. static Boolean IsWordStart (Ptr str, short len, short offset)
  178. {
  179.     OffsetTable offsets;
  180.     short offFirst, offSecond;
  181.  
  182.     FindWord(str, len, offset, true, nil, offsets);
  183.     offFirst = offsets[0].offFirst;
  184.     offSecond = offsets[0].offSecond;
  185.     if (offFirst != offset) return false;
  186.     if (offSecond > offFirst + 1) return true;
  187.     return CharIsAWord(str, offFirst);
  188. }
  189.  
  190.  
  191.  
  192. /*----------------------------------------------------------------------------
  193.     IsWordEnd 
  194.     
  195.     Determine whether or not a given offset in a string of text is the 
  196.     end of a word.
  197.     
  198.     Entry:    str = pointer to string.
  199.             len = length of string.
  200.             offset = offset in string.
  201.     
  202.     Exit:    function result = true if offset is at end of a word.
  203. ----------------------------------------------------------------------------*/
  204.  
  205. static Boolean IsWordEnd (Ptr str, short len, short offset)
  206. {
  207.     OffsetTable offsets;
  208.     short offFirst, offSecond;
  209.  
  210.     FindWord(str, len, offset, false, nil, offsets);
  211.     offFirst = offsets[0].offFirst;
  212.     offSecond = offsets[0].offSecond;
  213.     if (offset != offSecond) return false;
  214.     if (offSecond > offFirst + 1) return true;
  215.     return CharIsAWord(str, offFirst);
  216. }
  217.  
  218.  
  219.  
  220. /*----------------------------------------------------------------------------
  221.     MyTEDelete 
  222.     
  223.     Delete text intelligently.
  224.     
  225.     Entry:    theTE = handle to TextEdit record.
  226.             cut = true to cut text to clipboard, false to just delete it.
  227.     
  228.     Exit:    *extraSpaceDeleted = true if an extra space character was deleted
  229.                 before or after the selected text.
  230.     
  231.     Adapted from Apple's "DragText" sample code.
  232. ----------------------------------------------------------------------------*/
  233.  
  234. void MyTEDelete (TEHandle theTE, Boolean cut, Boolean *extraSpaceDeleted)
  235. {    
  236.     short selStart, selEnd, teLength;
  237.     Handle hText;
  238.     char state;
  239.     Boolean selStartsWithWord, selEndsWithWord;
  240.         
  241.     selStart = (**theTE).selStart;
  242.     selEnd = (**theTE).selEnd;
  243.     if (selStart >= selEnd) return;
  244.     teLength = (**theTE).teLength;
  245.     hText = (**theTE).hText;
  246.     
  247.     state = MyHGetState(hText);
  248.     HLock(hText);
  249.     selStartsWithWord = IsWordStart(*hText, teLength, selStart);
  250.     selEndsWithWord = IsWordEnd(*hText, teLength, selEnd);
  251.     MyHSetState(hText, state);
  252.     
  253.     if (cut) {
  254.         TECut(theTE);
  255.         ZeroScrap();
  256.         TEToScrap();
  257.     } else {
  258.         TEDelete(theTE);
  259.     }
  260.     
  261.     *extraSpaceDeleted = false;
  262.     if (selStartsWithWord && selEndsWithWord) {
  263.         if (selStart > 0 && (*hText)[selStart-1] == ' ') {
  264.             TESetSelect(selStart-1, selStart, theTE);
  265.             TEDelete(theTE);
  266.             *extraSpaceDeleted = true;
  267.         } else if (selEnd < teLength && (*hText)[selStart] == ' ') {
  268.             TESetSelect(selStart, selStart+1, theTE);
  269.             TEDelete(theTE);
  270.             *extraSpaceDeleted = true;
  271.         }
  272.     }
  273. }
  274.  
  275.  
  276.  
  277. /*----------------------------------------------------------------------------
  278.     MyTECut 
  279.     
  280.     Cut text intelligently.
  281.     
  282.     Entry:    theTE = handle to TextEdit record.
  283.     
  284.     Adapted from Apple's "DragText" sample code.
  285. ----------------------------------------------------------------------------*/
  286.  
  287. void MyTECut (TEHandle theTE)
  288. {    
  289.     Boolean extraSpaceDeleted;
  290.  
  291.     MyTEDelete(theTE, true, &extraSpaceDeleted);
  292. }
  293.  
  294.  
  295.  
  296. /*----------------------------------------------------------------------------
  297.     MyTECopy 
  298.     
  299.     Copy text.
  300.             
  301.     Entry:    theTE = handle to TextEdit record.
  302. ----------------------------------------------------------------------------*/
  303.  
  304. void MyTECopy (TEHandle theTE)
  305. {
  306.     TECopy(theTE);
  307.     ZeroScrap();
  308.     TEToScrap();
  309. }
  310.  
  311.  
  312.  
  313. /*----------------------------------------------------------------------------
  314.     MyTEPaste 
  315.     
  316.     Paste text intelligently.
  317.             
  318.     Entry:    text = pointer to text to be pasted, or nil if the
  319.                 text to be pasted is in the TextEdit scrap.
  320.             len = length of text to be pasted.
  321.             theTE = handle to TextEdit record.
  322.             maxLen = maximum legal length for this TextEdit field.
  323.             
  324.     Exit:    *extraSpaceAddedInFront = true if extra space character added
  325.                 in front of pasted text.
  326.     
  327.     Adapted from Apple's "DragText" sample code.
  328. ----------------------------------------------------------------------------*/
  329.  
  330. void MyTEPaste (Ptr text, short len, TEHandle theTE, short maxLen, 
  331.     Boolean *extraSpaceAddedInFront)
  332. {
  333.     Handle h;
  334.     char state;
  335.     Boolean newTextStartsWithWord, newTextEndsWithWord;
  336.     short selStart, selEnd, teLength;
  337.     Handle hText;
  338.     Boolean wordPreceedsSel, wordFollowsSel;
  339.     Boolean roomForExtraSpace;
  340.     
  341.     *extraSpaceAddedInFront = false;
  342.     
  343.     if (text == nil) len = MyTEGetScrapLen();
  344.      if (len == 0) return;
  345.     
  346.     if (text == nil) {
  347.         h = TEScrapHandle();
  348.         state = MyHGetState(h);
  349.         HLock(h);
  350.         newTextStartsWithWord = IsWordStart(*h, len, 0);
  351.         newTextEndsWithWord = IsWordEnd(*h, len, len);
  352.         MyHSetState(h, state);
  353.     } else {
  354.         newTextStartsWithWord = IsWordStart(text, len, 0);
  355.         newTextEndsWithWord = IsWordEnd(text, len, len);
  356.     }
  357.     
  358.     if (newTextStartsWithWord && newTextEndsWithWord) {
  359.         
  360.         selStart = (**theTE).selStart;
  361.         selEnd = (**theTE).selEnd;
  362.         teLength = (**theTE).teLength;
  363.         hText = (**theTE).hText;
  364.         
  365.         state = MyHGetState(hText);
  366.         HLock(hText);
  367.         wordPreceedsSel = selStart > 0 && IsWordEnd(*hText, teLength, selStart);
  368.         wordFollowsSel = selEnd < teLength && IsWordStart(*hText, teLength, selEnd);
  369.         MyHSetState(hText, state);
  370.         
  371.         roomForExtraSpace = teLength - (selEnd - selStart) + len < maxLen;
  372.         
  373.         if (roomForExtraSpace) {
  374.             if (wordPreceedsSel) {
  375.                 TEKey(' ', theTE);
  376.                 *extraSpaceAddedInFront = true;
  377.             } else if (wordFollowsSel) {
  378.                 TEKey(' ', theTE);
  379.                 TESetSelect(selStart, selStart, theTE);
  380.             }
  381.         }
  382.         
  383.     }
  384.     
  385.     if (text == nil) {
  386.         TEPaste(theTE);
  387.     } else {
  388.         TEDelete(theTE);
  389.         TEInsert(text, len, theTE);
  390.     }
  391. }
  392.  
  393.  
  394.  
  395. /*----------------------------------------------------------------------------
  396.     PtInTEHiliteRgn 
  397.     
  398.     Determine whether or not a point is in the current TextEdit hilite
  399.     region.
  400.             
  401.     Entry:    where = point in local coords.
  402.             theTE = handle to TextEdit record.
  403.             
  404.     Exit:    function result = true if point is in the hilite region.
  405. ----------------------------------------------------------------------------*/
  406.  
  407. Boolean PtInTEHiliteRgn (Point where, TEHandle theTE)
  408. {
  409.     Boolean result = false;
  410.     RgnHandle rgn = nil;
  411.     OSErr err = noErr;
  412.     
  413.     if (!HaveTEGetHiliteRgn()) return false;
  414.     rgn = NewRgn();
  415.     err = TEGetHiliteRgn(rgn, theTE);
  416.     if (err != noErr) goto exit;
  417.     result = PtInRgn(where, rgn);
  418.     
  419. exit:
  420.  
  421.     if (rgn != nil) DisposeRgn(rgn);
  422.     return result;
  423. }
  424.  
  425.  
  426.  
  427. /*----------------------------------------------------------------------------
  428.     SubtractTEHiliteRgn 
  429.     
  430.     Subtract a TE hilite region from another region.
  431.             
  432.     Entry:    rgn = region handle, in global coordinates.
  433.             theTE = handle to TextEdit record.
  434. ----------------------------------------------------------------------------*/
  435.  
  436. void SubtractTEHiliteRgn (RgnHandle rgn, TEHandle theTE)
  437. {
  438.     RgnHandle hiliteRgn = nil;
  439.     OSErr err = noErr;
  440.     
  441.     if (!HaveTEGetHiliteRgn()) return;
  442.     hiliteRgn = NewRgn();
  443.     err = TEGetHiliteRgn(hiliteRgn, theTE);
  444.     if (err != noErr) goto exit;
  445.     LocalToGlobalRgn(hiliteRgn);
  446.     DiffRgn(rgn, hiliteRgn, rgn);
  447.     
  448. exit:
  449.  
  450.     if (hiliteRgn != nil) DisposeRgn(hiliteRgn);
  451. }
  452.  
  453.  
  454.  
  455. /*----------------------------------------------------------------------------
  456.     DrawTECaret 
  457.     
  458.     Draw a caret at a given offset into a TextEdit field.
  459.             
  460.     Entry:    offset = offset into field.
  461.             theTE = handle to TextEdit record.
  462.             
  463.     If a caret is already drawn at this location, it is erased.
  464.     
  465.     Adapted from Apple's "DragText" sample code.
  466. ----------------------------------------------------------------------------*/
  467.  
  468. void DrawTECaret (short offset, TEHandle theTE)
  469. {
  470.     short lineHeight, teLength;
  471.     Handle hText;
  472.     Point where;
  473.     short pnMode;
  474.     
  475.     lineHeight = (**theTE).lineHeight;
  476.     teLength = (**theTE).teLength;
  477.     hText = (**theTE).hText;
  478.     where = TEGetPoint(offset, theTE);
  479.     if (offset == teLength && teLength > 0 && (*hText)[teLength - 1] == CR)
  480.         where.v += lineHeight;
  481.     pnMode = qd.thePort->pnMode;
  482.     PenMode(patXor);
  483.     MoveTo(where.h - 1, where.v - 1);
  484.     Line(0, 1 - lineHeight);
  485.     PenMode(pnMode);
  486. }
  487.  
  488.  
  489.  
  490. /*----------------------------------------------------------------------------
  491.     TEIsFrontOfLine 
  492.     
  493.     Determine whether an offset in a TextEdit field is at the beginning of a
  494.     line.
  495.             
  496.     Entry:    offset = offset into field.
  497.             theTE = handle to TextEdit record.
  498.             
  499.     Exit:    function result = true if offset is at beginning of line.
  500.     
  501.     Adapted from Apple's "DragText" sample code.
  502. ----------------------------------------------------------------------------*/
  503.  
  504. Boolean TEIsFrontOfLine (short offset, TEHandle theTE)
  505. {    
  506.     short line = 0, teLength;
  507.     Handle hText;
  508.     short *lineStarts;
  509.  
  510.     teLength = (**theTE).teLength;
  511.     hText = (**theTE).hText;
  512.     lineStarts = (**theTE).lineStarts;
  513.  
  514.     if (teLength == 0) return true;
  515.  
  516.     if (offset >= teLength)
  517.         return (*hText)[teLength - 1] == CR;
  518.  
  519.     while (lineStarts[line] < offset) line++;
  520.  
  521.     return lineStarts[line] == offset;
  522. }
  523.  
  524.  
  525.  
  526. /*----------------------------------------------------------------------------
  527.     MyTEGetOffset 
  528.     
  529.     Get the offset in the text corresponding to a point in a TextEdit field.
  530.             
  531.     Entry:    where = point in local coords.
  532.             theTE = handle to TextEdit record.
  533.             
  534.     Exit:    function result = offset into text.
  535.     
  536.     Adapted from Apple's "DragText" sample code.
  537. ----------------------------------------------------------------------------*/
  538.  
  539. short MyTEGetOffset (Point where, TEHandle theTE)
  540. {
  541.     short offset;
  542.     Handle hText;
  543.     char prevChar;
  544.     Point pt;
  545.     
  546.     offset = TEGetOffset(where, theTE);
  547.     hText = (**theTE).hText;
  548.     if (!TEIsFrontOfLine(offset, theTE)) return offset;
  549.     if (offset == 0) return offset;
  550.     prevChar = (*hText)[offset-1];
  551.     if (prevChar == CR) return offset;
  552.     if (!isLWSP(prevChar)) return offset;
  553.     pt = TEGetPoint(offset-1, theTE);
  554.     if (pt.h >= where.h) return offset;
  555.     return offset - 1;
  556. }
  557.  
  558.  
  559.  
  560. /*----------------------------------------------------------------------------
  561.     MyTEActivate 
  562.     
  563.     Activate a TextEdit field, but only if the window containing the field
  564.     is active.
  565.             
  566.     Entry:    theTE = handle to TextEdit record.
  567. ----------------------------------------------------------------------------*/
  568.  
  569. void MyTEActivate (TEHandle theTE)
  570. {
  571.     WindowPeek windPeek;
  572.  
  573.     windPeek = (WindowPeek)(**theTE).inPort;
  574.     if (windPeek->hilited) TEActivate(theTE);
  575. }
  576.  
  577.  
  578.  
  579. /*----------------------------------------------------------------------------
  580.     GetBol 
  581.     
  582.     Get the beginning of the line containing a character.
  583.             
  584.     Entry:    offset = offset into text of character.
  585.             clikStuff = 0 if offset on line boundary is at end of
  586.                 previous line, non-zero if offset on line boundary
  587.                 is at beginning of next line. 
  588.             theTE = handle to TextEdit record.
  589.             
  590.     Exit:    function result = offset into text of beginning of line 
  591.                 containing the character.
  592. ----------------------------------------------------------------------------*/
  593.  
  594. static short GetBol (short offset, short clikStuff, TEHandle theTE)
  595. {
  596.     short *lineStarts;
  597.     short teLength;
  598.  
  599.     teLength = (**theTE).teLength;
  600.     lineStarts = (**theTE).lineStarts;
  601.     if (offset <= 0) {
  602.         return 0;
  603.     } else if (offset >= teLength) {
  604.         offset = teLength;
  605.     }
  606.     while (*lineStarts < offset) lineStarts++;
  607.     if (*lineStarts == offset && clikStuff != 0) {
  608.         return *lineStarts;
  609.     } else {
  610.         return *(lineStarts-1);
  611.     }
  612. }
  613.  
  614.  
  615.  
  616. /*----------------------------------------------------------------------------
  617.     GetEol 
  618.     
  619.     Get the end of the line containing a character.
  620.             
  621.     Entry:    offset = offset into text of character.
  622.             clikStuff = 0 if offset on line boundary is at end of
  623.                 previous line, non-zero if offset on line boundary
  624.                 is at beginning of next line. 
  625.             theTE = handle to TextEdit record.
  626.             
  627.     Exit:    function result = offset into text of end of line 
  628.                 containing the character.
  629. ----------------------------------------------------------------------------*/
  630.  
  631. static short GetEol (short offset, short clikStuff, TEHandle theTE)
  632. {
  633.     short *lineStarts;
  634.     short teLength;
  635.  
  636.     teLength = (**theTE).teLength;
  637.     lineStarts = (**theTE).lineStarts;
  638.     if (offset <= 0) {
  639.         offset = 0;
  640.         clikStuff = 0xffff;
  641.     } else if (offset >= teLength) {
  642.         return teLength;
  643.     }
  644.     while (*lineStarts < offset) lineStarts++;
  645.     if (*lineStarts == offset && clikStuff != 0) {
  646.         return *(lineStarts+1);
  647.     } else {
  648.         return *lineStarts;
  649.     }
  650. }
  651.  
  652.  
  653.  
  654. /*----------------------------------------------------------------------------
  655.     GetTEPageHeight 
  656.     
  657.     Get the page height for a TextEdit field - the number of lines in
  658.     the view rectangle minus 1.
  659.             
  660.     Entry:    theTE = handle to TextEdit record.
  661.             
  662.     Exit:    function result = page height
  663. ----------------------------------------------------------------------------*/
  664.  
  665. short GetTEPageHeight (TEHandle theTE)
  666. {
  667.     short lineHeight;
  668.     Rect viewRect;
  669.  
  670.     lineHeight = (**theTE).lineHeight;
  671.     viewRect = (**theTE).viewRect;
  672.     return (viewRect.bottom - viewRect.top) / lineHeight - 1;
  673. }
  674.  
  675.  
  676.  
  677. /*----------------------------------------------------------------------------
  678.     GetBop 
  679.     
  680.     Get the beginning of the page.
  681.             
  682.     Entry:    offset = offset into text of character.
  683.             theTE = handle to TextEdit record.
  684.             pageHeight = number of lines in a page, or 0 to
  685.                 compute this based on the assumption that the
  686.                 view rectangle of the TextEdit field is a page.
  687.             
  688.     Exit:    function result = offset into text of beginning of page 
  689.                 containing the character, or beginning of previous
  690.                 page if already at top of page.
  691. ----------------------------------------------------------------------------*/
  692.  
  693. static short GetBop (short offset, TEHandle theTE, short pageHeight)
  694. {
  695.     Rect viewRect;
  696.     Point where;
  697.     short bop;
  698.     short *lineStarts, *p;
  699.     
  700.     viewRect = (**theTE).viewRect;
  701.     where.h = viewRect.left + 2;
  702.     where.v = viewRect.top + 2;
  703.     bop = MyTEGetOffset(where, theTE);
  704.     if (bop < offset) return bop;
  705.     if (pageHeight == 0) pageHeight = GetTEPageHeight(theTE);
  706.     lineStarts = (**theTE).lineStarts;
  707.     p = lineStarts;
  708.     while (*p < bop) p++;
  709.     p -= pageHeight;
  710.     if (p < lineStarts) p = lineStarts;
  711.     return *p;
  712. }
  713.  
  714.  
  715.  
  716. /*----------------------------------------------------------------------------
  717.     GetEop 
  718.     
  719.     Get the end of the page.
  720.             
  721.     Entry:    offset = offset into text of character.
  722.             theTE = handle to TextEdit record.
  723.             pageHeight = number of lines in a page, or 0 to
  724.                 compute this based on the assumption that the
  725.                 view rectangle of the TextEdit field is a page.
  726.             
  727.     Exit:    function result = offset into text of end of page 
  728.                 containing the character, or end of next
  729.                 page if already at end of page.
  730. ----------------------------------------------------------------------------*/
  731.  
  732. static short GetEop (short offset, TEHandle theTE, short pageHeight)
  733. {
  734.     Rect viewRect;
  735.     Point where;
  736.     short eop;
  737.     short *lineStarts, *p, *lineStartsEnd;
  738.     
  739.     viewRect = (**theTE).viewRect;
  740.     where.h = viewRect.right - 2;
  741.     where.v = viewRect.bottom - 2;
  742.     eop = MyTEGetOffset(where, theTE);
  743.     if (eop > offset) return eop;
  744.     if (pageHeight == 0) pageHeight = GetTEPageHeight(theTE);
  745.     lineStarts = (**theTE).lineStarts;
  746.     lineStartsEnd = lineStarts + (**theTE).nLines;
  747.     p = lineStarts;
  748.     while (*p < eop) p++;
  749.     p += pageHeight;
  750.     if (p > lineStartsEnd) p = lineStartsEnd;
  751.     return *p;
  752. }
  753.  
  754.  
  755.  
  756. /*----------------------------------------------------------------------------
  757.     GetPixelsFromBolGivenOffset 
  758.     
  759.     Get the the number of pixels from the beginning of a line to a given
  760.     character in the line.
  761.             
  762.     Entry:    bol = offset into text of beginning of line containing the
  763.                 character.
  764.             offset = offset into text of character.
  765.             theTE = handle to TextEdit record.
  766.             
  767.     Exit:    function result = number of pixels from the beginning of the
  768.                 line to the character.
  769. ----------------------------------------------------------------------------*/
  770.  
  771. static short GetPixelsFromBolGivenOffset (short bol, short offset, TEHandle theTE)
  772. {
  773.     Handle hText;
  774.     char state;
  775.     short result;
  776.  
  777.     hText = (**theTE).hText;
  778.     state = MyHGetState(hText);
  779.     HLock(hText);
  780.     result = TextWidth(*hText, bol, offset-bol);
  781.     MyHSetState(hText, state);
  782.     return result;
  783. }
  784.  
  785.  
  786.  
  787. /*----------------------------------------------------------------------------
  788.     GetOffsetGivenPixelsFromBol 
  789.     
  790.     Get the offset of the character which is a given number of pixels into 
  791.     a line.
  792.             
  793.     Entry:    bol = offset into text of beginning of line.
  794.             pixelsFromBol = number of pixels.
  795.             theTE = handle to TextEdit record.
  796.             
  797.     Exit:    function result = offset into text of the character which is
  798.                 the given number of pixels into the line.
  799. ----------------------------------------------------------------------------*/
  800.  
  801. static short GetOffsetGivenPixelsFromBol (short bol, short pixelsFromBol, TEHandle theTE)
  802. {
  803.     Handle hText;
  804.     char state;
  805.     short result;
  806.     short eol;
  807.  
  808.     hText = (**theTE).hText;
  809.     state = MyHGetState(hText);
  810.     HLock(hText);
  811.     
  812.     eol = GetEol(bol, 0xffff, theTE);
  813.     result = bol;
  814.     while (result < eol && TextWidth(*hText, bol, result - bol) < pixelsFromBol) result++;
  815.     
  816.     MyHSetState(hText, state);
  817.     return result;
  818. }
  819.  
  820.  
  821.  
  822. /*----------------------------------------------------------------------------
  823.     MyGetClikStuff 
  824.     
  825.     Get the clikStuff field from an edit record, adjusted if the insertion
  826.     point follows a final CR in the field. 
  827.             
  828.     Entry:    theTE = handle to TextEdit record.
  829.             
  830.     Exit:    function result = adjusted clikStuff field.
  831. ----------------------------------------------------------------------------*/
  832.  
  833. short MyGetClikStuff (TEHandle theTE)
  834. {
  835.     short selStart, selEnd, clikStuff, teLength;
  836.     Handle hText;
  837.     
  838.     selStart = (**theTE).selStart;
  839.     selEnd = (**theTE).selEnd;
  840.     clikStuff = (**theTE).clikStuff;
  841.     hText = (**theTE).hText;
  842.     teLength = (**theTE).teLength;
  843.     if (selStart == selEnd && selStart == teLength) {
  844.         if (clikStuff == 0 && selStart > 0 && *(*hText + selStart - 1) == CR) {
  845.             (**theTE).clikStuff = clikStuff = 0xffff;
  846.         } else if (clikStuff != 0 && (selStart == 0 || *(*hText + selStart - 1) != CR)) {
  847.             (**theTE).clikStuff = clikStuff = 0;
  848.         }
  849.     }
  850.     return clikStuff;
  851. }
  852.  
  853.  
  854.  
  855. /*----------------------------------------------------------------------------
  856.     MyGetTEHiliteOrCaretRgn 
  857.     
  858.     Get the current hilite region for a TextEdit field if there is
  859.     a selection range, or a region containing the caret if there
  860.     is no selection range.
  861.             
  862.     Entry:    rgn = handle to region.
  863.             theTE = handle to TextEdit record.
  864.                 
  865.     Exit:    rgn modified to contain hilite or caret region.
  866. ----------------------------------------------------------------------------*/
  867.  
  868. static void MyGetTEHiliteOrCaretRgn (RgnHandle rgn, TEHandle theTE)
  869. {
  870.     short selStart, selEnd, lineHeight;
  871.     Point where;
  872.     
  873.     selStart = (**theTE).selStart;
  874.     selEnd = (**theTE).selEnd;
  875.     lineHeight = (**theTE).lineHeight;
  876.     if (selStart == selEnd) {
  877.         where = TEGetPoint(selStart, theTE);
  878.         SetRectRgn(rgn, where.h - 1, where.v - lineHeight, where.h + 1, where.v);
  879.     } else {
  880.         TEGetHiliteRgn(rgn, theTE);
  881.     }
  882. }
  883.  
  884.  
  885.  
  886. /*----------------------------------------------------------------------------
  887.     TEArrowKey 
  888.     
  889.     Handle arrow keys in TextEdit windows.
  890.             
  891.     Entry:    theChar = leftArrow, rightArrow, upArrow, or downArrow.
  892.             modifiers = modifiers field from event record.
  893.             theTE = handle to TextEdit record.
  894.             pageHeight = number of lines in a page, or 0 to have TEArrowKey
  895.                 compute this itself based on the assumption that the
  896.                 view rectangle of the TextEdit field is a page.
  897.             prevEvent = pointer to previous event.
  898.                 
  899.     Exit:    selection range adjusted in the TextEdit record.
  900.             *scrollIntoView = offset into TextEdit record which
  901.                 should be scrolled into view.
  902.     
  903.     Warning: The function proves that if you hack long enough, you can make
  904.     TextEdit do anything you want it to do, but only at the risk of tying 
  905.     your brain into complete knots. This code is incredibly complicated and 
  906.     sensitive.
  907. ----------------------------------------------------------------------------*/
  908.  
  909. void TEArrowKey (char theChar, short modifiers, TEHandle theTE, 
  910.     short pageHeight, EventRecord *prevEvent, short *scrollIntoView)
  911. {
  912.     short selStart, selEnd, teLength, offset, bol, eol;
  913.     Boolean shift, option, command;
  914.     Boolean prevWasArrowKey;
  915.     char prevChar;
  916.     short prevModifiers;
  917.     Boolean isUpDown;
  918.     Boolean shiftArrowSequence, upDownArrowSequence;
  919.     Handle hText;
  920.     Boolean haveTEOutlineHilite;
  921.     short savedOutlineHilite;
  922.     OffsetTable wordOffsets;
  923.     char state;
  924.     char c;
  925.     short newSelStart, newSelEnd, oldOffset;
  926.     static RgnHandle selRgnOld = nil, selRgnNew = nil;
  927.     static short anchor = 0;
  928.     static short upDownPixelsFromBol = 0;
  929.     static short clikStuff = 0xffff;
  930.     
  931.     selStart = (**theTE).selStart;
  932.     selEnd = (**theTE).selEnd;
  933.     teLength = (**theTE).teLength;
  934.     hText = (**theTE).hText;
  935.     shift = (modifiers & shiftKey) != 0;
  936.     option = (modifiers & optionKey) != 0;
  937.     command = (modifiers & cmdKey) != 0;
  938.     isUpDown = theChar == upArrow || theChar == downArrow;
  939.     prevChar = prevEvent->message & 0xff;
  940.     prevModifiers = prevEvent->modifiers;
  941.     prevWasArrowKey = (prevEvent->what == keyDown || prevEvent->what == autoKey) &&
  942.         IsArrowKey(prevChar);
  943.     shiftArrowSequence = shift && prevWasArrowKey && 
  944.         (prevModifiers & shiftKey) != 0; 
  945.     upDownArrowSequence = isUpDown && prevWasArrowKey && !option && !command && 
  946.         (prevChar == upArrow || prevChar == downArrow) &&
  947.         (prevModifiers & optionKey) == 0 &&
  948.         (prevModifiers & cmdKey) == 0;
  949.     
  950.     if (shift && !shiftArrowSequence)
  951.         anchor = theChar == upArrow || theChar == leftArrow ?
  952.             selEnd : selStart;
  953.         
  954.     if (shiftArrowSequence) {
  955.         offset = anchor == selStart ? selEnd : selStart;
  956.     } else {
  957.         offset = theChar == upArrow || theChar == leftArrow ? selStart : selEnd;
  958.     }
  959.     
  960.     oldOffset = offset;
  961.     
  962.     if (!option && !command) {
  963.     
  964.         if (isUpDown && !upDownArrowSequence) {
  965.             clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  966.             bol = GetBol(offset, clikStuff, theTE);
  967.             upDownPixelsFromBol = GetPixelsFromBolGivenOffset(bol, offset, theTE);
  968.         }
  969.     
  970.         switch (theChar) {
  971.             case leftArrow:
  972.                 if (shift || selStart == selEnd) offset--;
  973.                 clikStuff = 0xffff;
  974.                 break;
  975.             case rightArrow:
  976.                 if (shift || selStart == selEnd) offset++;
  977.                 clikStuff = 0xffff;
  978.                 break;
  979.             case upArrow:
  980.                 if (shift && offset != anchor) {
  981.                     clikStuff = upDownPixelsFromBol > 0 ? 0 : 0xffff;
  982.                 } else {
  983.                     clikStuff = MyGetClikStuff(theTE);
  984.                 }
  985.                 bol = GetBol(offset, clikStuff, theTE);
  986.                 if (bol > 0) {
  987.                     bol = GetBol(bol, 0, theTE);
  988.                     offset = GetOffsetGivenPixelsFromBol(bol, upDownPixelsFromBol, theTE);
  989.                 } else if (offset == oldOffset) {
  990.                     offset = 0;
  991.                 }
  992.                 clikStuff = offset > bol ? 0 : 0xffff;
  993.                 break;
  994.             case downArrow:
  995.                 if (shift && offset != anchor) {
  996.                     clikStuff = upDownPixelsFromBol > 0 ? 0 : 0xffff;
  997.                 } else {
  998.                     clikStuff = MyGetClikStuff(theTE);
  999.                 }
  1000.                 bol = GetEol(offset, clikStuff, theTE);
  1001.                 if (bol < teLength || (shift && upDownPixelsFromBol == 0) ||
  1002.                     (teLength > 0 && *(*hText + teLength - 1) == CR))
  1003.                         offset = GetOffsetGivenPixelsFromBol(bol, upDownPixelsFromBol, theTE);
  1004.                 if (bol >= teLength && offset == oldOffset) {
  1005.                     offset = teLength;
  1006.                 }
  1007.                 clikStuff = offset > bol ? 0 : 0xffff;
  1008.                 break;
  1009.         }
  1010.  
  1011.     } else if (option && !command) {
  1012.     
  1013.         switch (theChar) {
  1014.             case leftArrow:
  1015.                 offset--;
  1016.                 while (offset >= 0) {
  1017.                     c = *(*hText + offset);
  1018.                     if (c < 0 || isalnum(c)) break;
  1019.                     offset--;
  1020.                 }
  1021.                 offset++;
  1022.                 state = MyHGetState(hText);
  1023.                 HLock(hText);
  1024.                 FindWord(*hText, teLength, offset, false, nil, wordOffsets);
  1025.                 MyHSetState(hText, state);
  1026.                 offset = wordOffsets[0].offFirst;
  1027.                 clikStuff = 0xffff;
  1028.                 break;
  1029.             case rightArrow:
  1030.                 while (offset < teLength) {
  1031.                     c = *(*hText + offset);
  1032.                     if (c < 0 || isalnum(c)) break;
  1033.                     offset++;
  1034.                 }
  1035.                 state = MyHGetState(hText);
  1036.                 HLock(hText);
  1037.                 FindWord(*hText, teLength, offset, true, nil, wordOffsets);
  1038.                 MyHSetState(hText, state);
  1039.                 offset = wordOffsets[0].offSecond;
  1040.                 clikStuff = 0;
  1041.                 break;
  1042.             case upArrow:
  1043.                 offset--;
  1044.                 while (offset >= 0 && isLWSPorCR(*(*hText + offset))) offset--;
  1045.                 while (offset >= 0) {
  1046.                     while (offset >= 0 && !isLWSPorCR(*(*hText + offset))) offset--;
  1047.                     if (offset < 0) break;
  1048.                     offset--;
  1049.                     while (offset >= 0 && isLWSP(*(*hText + offset))) offset--;
  1050.                     if (offset < 0 || *(*hText + offset) == CR) break;
  1051.                 }
  1052.                 offset++;
  1053.                 while (offset < teLength && isLWSPorCR(*(*hText + offset))) offset++;
  1054.                 clikStuff = 0xffff;
  1055.                 break;
  1056.             case downArrow:
  1057.                 while (offset < teLength && isLWSPorCR(*(*hText + offset))) offset++;
  1058.                 while (offset < teLength) {
  1059.                     while (offset < teLength && *(*hText + offset) != CR) offset++;
  1060.                     if (offset >= teLength) break;
  1061.                     offset++;
  1062.                     if (isLWSPorCR(*(*hText + offset))) break;
  1063.                 }
  1064.                 if (offset < teLength) {
  1065.                     offset--;
  1066.                     while (offset >= 0 && isLWSPorCR(*(*hText + offset))) offset--;
  1067.                     offset++;
  1068.                 }
  1069.                 clikStuff = 0;
  1070.                 break;
  1071.         }
  1072.         
  1073.     } else if (!option && command) {
  1074.     
  1075.         switch (theChar) {
  1076.             case leftArrow:
  1077.                 clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  1078.                 bol = GetBol(offset, clikStuff, theTE);
  1079.                 if (offset > bol) {
  1080.                     offset = bol;
  1081.                 } else {
  1082.                     offset = GetBol(offset, 0, theTE);
  1083.                 }
  1084.                 clikStuff = 0xffff;
  1085.                 break;
  1086.             case rightArrow:
  1087.                 clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  1088.                 eol = GetEol(offset, clikStuff, theTE);
  1089.                 if (offset < eol) {
  1090.                     if (offset == eol-1 && *(*hText + offset) == CR) {
  1091.                         offset = GetEol(eol, 0xffff, theTE);
  1092.                     } else {
  1093.                         offset = eol;
  1094.                     }
  1095.                 } else {
  1096.                     offset = GetEol(offset, 0xffff, theTE);
  1097.                 }
  1098.                 clikStuff = 0;
  1099.                 break;
  1100.             case upArrow:
  1101.                 offset = GetBop(offset, theTE, pageHeight);
  1102.                 clikStuff = 0xffff;
  1103.                 break;
  1104.             case downArrow:
  1105.                 offset = GetEop(offset, theTE, pageHeight);
  1106.                 clikStuff = 0;
  1107.                 break;
  1108.         }
  1109.     
  1110.     } else { /* option && command */
  1111.     
  1112.         switch (theChar) {
  1113.             case upArrow:
  1114.                 offset = 0;
  1115.                 clikStuff = 0xffff;
  1116.                 break;
  1117.             case downArrow:
  1118.                 offset = teLength;
  1119.                 clikStuff = 0;
  1120.                 break;
  1121.         }
  1122.     
  1123.     }
  1124.     
  1125.     if ((!shift || anchor == offset) && clikStuff == 0 && 
  1126.         offset > 0 && *(*hText + offset - 1) == CR) 
  1127.     {
  1128.         offset--;
  1129.         clikStuff = 0xffff;
  1130.     }
  1131.     
  1132.     if (offset < 0) {
  1133.         offset = 0;
  1134.     } else if (offset > teLength) {
  1135.         offset = teLength;
  1136.     }
  1137.     
  1138.     if (!shift) {
  1139.         if (MyGetClikStuff(theTE) != clikStuff && selStart == selEnd) {
  1140.             haveTEOutlineHilite = HaveTEOutlineHiliteFeature();
  1141.             if (haveTEOutlineHilite) 
  1142.                 savedOutlineHilite = TEFeatureFlag(teFOutlineHilite, TEBitClear, theTE);
  1143.             TEDeactivate(theTE);
  1144.             (**theTE).clikStuff = clikStuff;
  1145.             TESetSelect(offset, offset, theTE);
  1146.             TEActivate(theTE);
  1147.             if (haveTEOutlineHilite)
  1148.                 TEFeatureFlag(teFOutlineHilite, savedOutlineHilite, theTE);
  1149.         } else {
  1150.             (**theTE).clikStuff = clikStuff;
  1151.             TESetSelect(offset, offset, theTE);
  1152.         }
  1153.     } else {
  1154.         if (offset <= anchor) {
  1155.             newSelStart = offset;
  1156.             newSelEnd = anchor;
  1157.         } else {
  1158.             newSelStart = anchor;
  1159.             newSelEnd = offset;
  1160.         }
  1161.         if (HaveTEGetHiliteRgn()) {
  1162.             if (selRgnOld == nil) selRgnOld = NewRgn();
  1163.             if (selRgnNew == nil) selRgnNew = NewRgn();
  1164.             MyGetTEHiliteOrCaretRgn(selRgnOld, theTE);
  1165.             (**theTE).selStart = newSelStart;
  1166.             (**theTE).selEnd = newSelEnd;
  1167.             MyGetTEHiliteOrCaretRgn(selRgnNew, theTE);
  1168.             (**theTE).selStart = selStart;
  1169.             (**theTE).selEnd = selEnd;
  1170.             if (selStart == selEnd || newSelStart == newSelEnd) {
  1171.                 UnionRgn(selRgnNew, selRgnOld, selRgnNew);
  1172.             } else {
  1173.                 XorRgn(selRgnNew, selRgnOld, selRgnNew);
  1174.             }
  1175.             SetClip(selRgnNew);
  1176.         }
  1177.         TESetSelect(newSelStart, newSelEnd, theTE);
  1178.         ClipRect(&qd.thePort->portRect);
  1179.     }
  1180.     
  1181.     *scrollIntoView = offset;
  1182. }
  1183.  
  1184.  
  1185.  
  1186. /*----------------------------------------------------------------------------
  1187.     IsArrowKey 
  1188.     
  1189.     Check to see if a character is an arrow key.
  1190.             
  1191.     Entry:    theChar = character.
  1192.                 
  1193.     Exit:    function result = true if arrow key.
  1194. ----------------------------------------------------------------------------*/
  1195.  
  1196. Boolean IsArrowKey (char theChar)
  1197. {
  1198.     return theChar == upArrow || theChar == downArrow ||
  1199.         theChar == leftArrow || theChar == rightArrow;
  1200. }